package com.lotusfall.bank.utils;

import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.zip.ZipFile;
// mybatis内置了javassist
// mybatis 号称轻量级，只需要一个jar包就行

/**
 * 工具类：可以动态生成DAO的实现类（动态生成DAO的代理类）
 * @author lotus
 * @version 1.0
 * @since 1.0
 */
public class GenerateDaoProxy {  // 先看做是mybatis框架实现的

    /**
     * 生成DAO接口实现类，并且将实现类的对象创建出来并返回
     * @param daoInterface DAO接口
     * @return DAO接口实现类的实例化对象
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface){
        // 类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类(com.lotusfall.bank.dao.AccountDao  ->> com.lotusfall.bank.dao.AccountDaoProxy)
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy" );// 实际本质上就是在内存中生成一个代理类。
        // 制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName() );
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中的所有方法
        Method[] declaredMethods = daoInterface.getDeclaredMethods();
        Arrays.stream(declaredMethods).forEach(method ->{
            // method是接口中的抽象方法
            // 将method抽象方法实现
            try {
                // public Account selectByActno(String actno){ 代码; }
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName() + " ");
                methodCode.append(method.getName());
                methodCode.append("(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getName());
                    methodCode.append(" ");
                    methodCode.append("arg" + i);
                    if(i != parameterTypes.length - 1){
                        methodCode.append(",");
                    }
                }
                methodCode.append(")");
                methodCode.append("{");
                // 需要方法体中的代码
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.lotusfall.bank.utils.SqlSessionUtil.openSession();\n");
                // 需要知道什么类型的sql语句b
                // sql语句的id是框架使用者提供的，具有多变性，对于框架开发者来说不知道
                // mybatis框架开发者出台了一个规定，凡是使用GenerateDaoProxy sqlId都不能随便写
                // namespace必须是dao接口的全限定名，id必须是dao接口中方法名！！！
                String sqlId = daoInterface.getName() + "." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if (sqlCommandType == SqlCommandType.INSERT) {
                    methodCode.append("sqlSession.selectOne(\"account.selectByActno\",actno);");
                }
                if (sqlCommandType == SqlCommandType.DELETE) {

                }
                if (sqlCommandType == SqlCommandType.UPDATE) {
                    // 注意传入参数 arg + i
                    methodCode.append("return sqlSession.update( \"" + sqlId +"\",arg0);");
                }
                if (sqlCommandType == SqlCommandType.SELECT) {
                    // 强转
                    String returnType = method.getReturnType().getName();
                    methodCode.append("return (" + returnType + ")sqlSession.selectOne( \"" + sqlId +"\",arg0);");
                }
                methodCode.append("}");

                CtMethod ctMethod = CtMethod.make(methodCode.toString(),ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 创建对象
        Object o = null;
        try {
            Class<?> aClass = ctClass.toClass();
            o = aClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return o;
    }
}
